/*-------------------------------------------------------------------------
 - Copyright (c) 2024-2025 [XD-AMCC TEAM]
 - [XD-AMCC] is licensed under Mulan PSL v2.
 - You can use this software according to the terms and conditions of the Mulan PSL v2.
 - You may obtain a copy of Mulan PSL v2 at:
 -          http://license.coscl.org.cn/MulanPSL2
 - THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES
 - OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
 - TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 - See the Mulan PSL v2 for more details.
 ------------------------------------------------------------------------*/


/**
 * @brief Implement operations for AMCC configuration, bootup and shutdown.
 *
 * @author  WXQ#XDU
 * @date    2024.11
 */

#include <stdlib.h>    // for atexit()
#include <stdbool.h>

#include "../include/amcc_config.h"
#include "../include/amcc_string.h"
#include "../include/amcc_log.h"
#include "amcc_error.h"


const char *  amcc_version_descript() {
    return AMCC_PRJ_FULLNAME
           " version " AMCC_VERSION_STRING
           "(Built by " AMCC_BY_NAME " "
           AMCC_BY_VERSTR
           " on " __DATE__ " at "  __TIME__
           ")";
}

const char *  amcc_build_info() {
    return "Built by " AMCC_BY_NAME " "
           AMCC_BY_VERSTR " on " __DATE__ " at " __TIME__;
}


const char * amcc_organization() {
    return "XiDian University";
}



//  structure of amcc configuration
typedef struct amcc_config {
    char       * appName;
    const char * inputFilePath;

    // Directory in where all output files are saved
    char *  outputDir;
    FILE *  errFileHandler;

    // log level, the default value is hardcode,
    // but can be specified by Command-Line-Argument.
    EnumLogLevel  logLevel;

    bool isLogSrcFile; // show amcc source file info in log
    bool isLogTime;    // show time-stamp in log
    bool isLogParserStack; // log RDF call stack of parser
    bool shouldExistMain;  // report error if no main() in source program
    const char * tempVarHeader; // header for temporary variable in IR-codes

    // path of logfile, the default value is hardcode,
    // but can be specified by Command-Line-Argument.
    char logFilePath[FILEPATH_MAXSIZE];
    char tokenFilePath[FILEPATH_MAXSIZE];
    char treeFilePath[FILEPATH_MAXSIZE];
    char symTableFilePath[FILEPATH_MAXSIZE];
    char ircodeFilePath[FILEPATH_MAXSIZE];

} t_amcc_config;


/**
 * @brief Type of functions that initialize the internal data of a module.
 *        These functions is invoked during AMCC bootup.
 *
 * @return On success, return a non-negative number.
 * @return On any failure, return a negative number.
 */
typedef int  (*FP_MODULE_INIT)();

/**
 * @brief Type of functions that clean the internal data of a module.
 *        These functions is invoked during AMCC shutdown.
 */
typedef void (*FP_MODULE_CLEAN)();

//  an operation pair of a module for bootup and shutdown
typedef struct {
    const char      *  module_name;
    FP_MODULE_INIT     func_module_init;
    FP_MODULE_CLEAN    func_module_clean;
} t_bootop_pair;


extern int  log_module_init();          // in log.c
extern void log_module_clean();
extern int  lexer_module_init();        // in lexer.c
extern void lexer_module_clean();
extern int  parser_module_init();       // in parser.c
extern void parser_module_clean();
extern int  tree_module_init();         // in tree.c
extern void tree_module_clean();
extern int  typesys_module_init();      // in typesys.c
extern void typesys_module_clean();
extern int  symtable_module_init();     // in symtable.c
extern void symtable_module_clean();
extern int  emitter_module_init();       // in emitter.c
extern void emitter_module_clean();

// modular operation pairs for bootup and shutdown
static t_bootop_pair  amcc_boot_ops [] = {
     { "LEXER"     , lexer_module_init    , lexer_module_clean    }
    ,{ "PARSE"     , parser_module_init   , parser_module_clean   }
    ,{ "TREE"      , tree_module_init     , tree_module_clean     }
    ,{ "TYPESYS"   , typesys_module_init  , typesys_module_clean  }
    ,{ "SYMTABLE"  , symtable_module_init , symtable_module_clean }
    ,{ "EMITTER"   , emitter_module_init  , emitter_module_clean  }
};



// Top level structure for AMCC boot and shutdown.
typedef struct {
    bool             S__amcc_ready;
    int              n_bootop_pairs;
    t_bootop_pair  * boot_ops;
    t_amcc_config    appConfig;
} t_glob_bootcfg;


static t_glob_bootcfg  gBootConfig = {
    .boot_ops       =  amcc_boot_ops,
    .n_bootop_pairs =  sizeof(amcc_boot_ops)
                       / sizeof(amcc_boot_ops[0])
};


#define SPELL_FILEPATH(dst, value, post)                \
    snprintf(gBootConfig.appConfig.dst,                 \
    sizeof(gBootConfig.appConfig.dst) - 1, "%s/%s%s",   \
    gBootConfig.appConfig.outputDir, value, post)

static void updateOutfilePath_ ( ) {
    SPELL_FILEPATH(logFilePath      ,  FN_LOGFILE_DFT    ,  "");
    SPELL_FILEPATH(tokenFilePath    ,  FN_TOKENFILE_DFT  ,  "");
    SPELL_FILEPATH(ircodeFilePath   ,  FN_IRCODEFILE_DFT ,  "");
    SPELL_FILEPATH(treeFilePath     ,  FN_TREEFILE_DFT   ,  "");
    SPELL_FILEPATH(symTableFilePath ,  FN_SYMFILE_DFT    ,  "");
}


static void deleteLastOutput_ ( ) {
    path_clear_dir(amcc_get_outputDir());
}


void amcc_boot0 (const char * appName) {
    gBootConfig.S__amcc_ready = false;

    // set default value of config items
    gBootConfig.appConfig.isLogSrcFile      = false;
    gBootConfig.appConfig.isLogTime         = false;
    gBootConfig.appConfig.isLogParserStack  = false;
    gBootConfig.appConfig.logLevel          = LOG_INFO;

    gBootConfig.appConfig.inputFilePath = NULL;
    gBootConfig.appConfig.appName = cstring_duplicate(
            (NULL != appName ? appName : "AMCC-based-APP" ));
    gBootConfig.appConfig.errFileHandler = stderr;

    amcc_set_outputDir(DIR_OUTPUT_DFT);
}



int  amcc_bootup ( ) {
    if ( gBootConfig.S__amcc_ready ) return 0;

    updateOutfilePath_ ( );
    deleteLastOutput_  ( );

    log_module_init ( );
    log_info("Starting app [%s] ...", gBootConfig.appConfig.appName);

    {
        char * pWorkDir = path_get_workdir(NULL, 0);
        log_info("Current working directory is [%s]", pWorkDir);
        free(pWorkDir);
    }

    int  result;
    for (int i = 0; i < gBootConfig.n_bootop_pairs; ++i) {
        t_bootop_pair * op = &(gBootConfig.boot_ops[i]);
        log_trace("Module [%s] is initializing", op->module_name);
        result = op->func_module_init ( );
        if (result < 0) {
            const char* fmt = "Module [%s] initialization FAILed with code %d";
            fprintf(stderr, fmt, op->module_name, result);
            fprintf( stderr, "\n");
            log_error(fmt, op->module_name, result);
            for (int k=i-1; k>=0; --k) { // shutdown modules that have booted
                gBootConfig.boot_ops[k].func_module_clean();
            }
            log_error("FAILed to initialize [%s], see above messages.",
                        gBootConfig.appConfig.appName);
            log_module_clean();
            return -1;
        }
        log_info("Module [%s] is ready", op->module_name);
    }

    atexit ( amcc_shutdown );
    gBootConfig.S__amcc_ready = true;
    log_info("Started app [%s] successfully.\n", gBootConfig.appConfig.appName);

    return 0;
}


void amcc_shutdown ( void ) {
    if ( ! gBootConfig.S__amcc_ready ) return;

    log_without_header("");
    log_info("Shutting down app [%s] ...", gBootConfig.appConfig.appName);

    for (int k = gBootConfig.n_bootop_pairs - 1; k >= 0; --k) {
        t_bootop_pair* op = &(gBootConfig.boot_ops[k]);
        log_trace("Module [%s] is cleaning", op->module_name);
        op->func_module_clean();
        log_info("Module [%s] has cleaned", op->module_name);
    }

    log_info("App [%s] has shutdown with %d error%s.\n\n"
             "//// This is the last log message. Good good study ^_^",
             gBootConfig.appConfig.appName,
             error_get_count(),
             (error_get_count() > 1 ? "s" : "") );
    log_module_clean();
    gBootConfig.S__amcc_ready = false;

    cstring_destroy(gBootConfig.appConfig.appName);
    cstring_destroy(gBootConfig.appConfig.outputDir);
}


// -----------------------------------------------------------------------
// Functions for configuration
//

void amcc_set_outputDir (const char * output_dir) {
    if (NULL == output_dir || '\0' == output_dir[0])
        return;

    gBootConfig.appConfig.outputDir = cstring_duplicate(output_dir);
    size_t len = strlen(output_dir);
    char  tailChar = gBootConfig.appConfig.outputDir[len - 1];
    if ('/' == tailChar || '\\' == tailChar) {
        gBootConfig.appConfig.outputDir[len - 1] = '\0';
    }
}

const char * amcc_get_outputDir ( ) {
    return gBootConfig.appConfig.outputDir;
}

void amcc_set_errorFile (FILE * fp) {
    gBootConfig.appConfig.errFileHandler = fp;
}

FILE * amcc_get_errorFile ( ) {
    return gBootConfig.appConfig.errFileHandler;
}

const char * amcc_get_appname ( ) {
    return gBootConfig.appConfig.appName;
}

void amcc_set_logLevel (int defaultLogLevel) {
    if( LOG_TRACE <= defaultLogLevel && defaultLogLevel <= LOG_FATAL )
        gBootConfig.appConfig.logLevel = defaultLogLevel;
}

int amcc_get_logLevel ( ) {
    return gBootConfig.appConfig.logLevel;
}

const char* amcc_get_logFile ( ) {
    return gBootConfig.appConfig.logFilePath;
}

void amcc_set_logSource (bool  isLogSource) {
    gBootConfig.appConfig.isLogSrcFile = isLogSource;
}

bool amcc_is_logSource ( ) {
    return gBootConfig.appConfig.isLogSrcFile;
}

void amcc_set_logTime (bool  isLogTime) {
    gBootConfig.appConfig.isLogTime = isLogTime;
}

bool amcc_is_logTime ( ) {
    return gBootConfig.appConfig.isLogTime;
}

void amcc_set_inputFilePath (const char* inFile) {
    gBootConfig.appConfig.inputFilePath = inFile;
}

const char* amcc_get_inputFilePath ( ) {
    return gBootConfig.appConfig.inputFilePath;
}

bool amcc_set_logParserStack (bool yesORno) {
    bool oldV = gBootConfig.appConfig.isLogParserStack;
    gBootConfig.appConfig.isLogParserStack = yesORno;
    return oldV;
}

bool amcc_is_logParerStack ( ) {
    return gBootConfig.appConfig.isLogParserStack;
}

const char* amcc_get_tokenFilePath ( ) {
    return gBootConfig.appConfig.tokenFilePath;
}

const char* amcc_get_treeFilePath ( ) {
    return gBootConfig.appConfig.treeFilePath;
}

const char* amcc_get_symbolFilePath ( ) {
    return gBootConfig.appConfig.symTableFilePath;
}

const char* amcc_get_irCodeFilePath ( ) {
    return gBootConfig.appConfig.ircodeFilePath;
}

bool amcc_shouldExistMain() {
    return gBootConfig.appConfig.shouldExistMain;
}

void amcc_set_shouldExistMain(bool onOff) {
    gBootConfig.appConfig.shouldExistMain = onOff;
}

const char * amcc_get_tempvar_prefix() {
    if (NULL == gBootConfig.appConfig.tempVarHeader)
        return "t";
    return gBootConfig.appConfig.tempVarHeader;
}

void amcc_set_tempvar_prefix(const char* prefix) {
    if (NULL == prefix || '\0' == prefix[0])
        gBootConfig.appConfig.tempVarHeader = NULL;
    gBootConfig.appConfig.tempVarHeader = prefix;
}